#!/usr/bin/env python3

from mastodon import Mastodon as M
from optparse import OptionParser
import sqlite3
import datetime
import json

from bs4 import BeautifulSoup
import lxml
from lxml import html
from lxml.html.clean import clean_html
from lxml import etree

import flask
from http.server import SimpleHTTPRequestHandler, HTTPServer
from flask import Flask, render_template

# https://stackoverflow.com/questions/56554159/typeerror-object-of-type-datetime-is-not-json-serializable-with-serialize-fu
class DateTimeEncoder(json.JSONEncoder):
    def default(self, z):
        if isinstance(z, datetime.datetime):
            return (str(z))
        else:
            return super().default(z)

import sys
import re

parser = OptionParser()
parser.add_option("-a", "--api-token", dest="apitoken",
                  help="use this APITOKEN", metavar="APITOKEN")
parser.add_option("-s", "--server", dest="server",
                  help="use this INSTANCE", metavar="INSTANCE")
parser.add_option("-d", "--db", dest="dbfile",
                  help="use this sqlite3 FILE", metavar="FILE", default="mastodon.db")

(options, args) = parser.parse_args()

if len(args) < 1:
    print("required: command", file=sys.stderr)
    sys.exit(-1)

if options.server is None:
    print("required option: -s", file=sys.stderr)
    sys.exit(-1)

if options.apitoken is None:
    print("required option: -a", file=sys.stderr)
    sys.exit(-1)

if options.dbfile is None:
    print("not using database")
else:
    connection = sqlite3.connect(options.dbfile)

commandline = " ".join(args[0:])

import pyparsing as pp
pp.ParserElement.enablePackrat()

DOWNLOAD, LOCALLY, GENERATE, SERVE = map(
    pp.CaselessKeyword, "download locally generate serve".split()
)

commandP = pp.Forward()

downloadCommand = pp.Forward()
downloadCommand <<= (
    DOWNLOAD
).setName("downloadCommand")

serveCommand = pp.Forward()
serveCommand <<= (
    SERVE
).setName("serveCommand")

commandP <<= pp.Group(
    downloadCommand | serveCommand 
)

comm = commandP.parse_string(commandline)
print(repr(comm))


cursor = connection.cursor()

def connect_mastodon():
    m = M(
        api_base_url=options.server,
        access_token=options.apitoken,
        ratelimit_pacefactor=2.0,
    )
    return m

def downloadmytoots(connection, cursor, m, myid):
    done = False
    maxid = None
    number = 0
    while not done:
        print("--- downloading <%s (total %d) ---" % (maxid, number))
        tranche = m.account_statuses(id=myid, max_id=maxid)
        if len(tranche) == 0:
            done = True
            break
        
        maxid = min([a["id"] for a in tranche])
        if maxid is not None:
            cursor.execute("SELECT COUNT() FROM toots WHERE id = :id", {"id": maxid})
            numresults = cursor.fetchone()[0]
            if numresults > 0:
                print("probably already downloaded")
                done = True

        for s in tranche:
            cursor.execute("SELECT COUNT() FROM toots WHERE id = :id", {"id": s["id"]})
            numresults = cursor.fetchone()[0]
            if numresults > 0:
                print("already present, not updating")
                done = True
                continue
            cursor.execute("INSERT INTO toots (id, url, text, full) VALUES (?,?,?,?)",
                           (s["id"], s["url"], s["content"], json.dumps(s, cls=DateTimeEncoder, default=str, sort_keys=True)))
            connection.commit()
        number += len(tranche)

def download_command():
    connection = sqlite3.connect(options.dbfile)
    cursor = connection.cursor()
    try:
        cursor.execute("CREATE TABLE toots (id TEXT, url TEXT, text TEXT, full TEXT)")
    except sqlite3.OperationalError as e:
        print("%s" % e)
    m = connect_mastodon()
    myid = m.me().id
    downloadmytoots(connection, cursor, m, myid)
    connection.close()

def reload_command(id):
    if id is None:
        return
    connection = sqlite3.connect(options.dbfile)
    cursor = connection.cursor()
    m = connect_mastodon()
    myid = m.me().id
    toot = m.status(id)
    if toot is not None:
        cursor.execute("DELETE FROM toots WHERE ID = ?", (toot["id"],))
        cursor.execute("INSERT INTO toots (id, url, text, full) VALUES (?,?,?,?)",
                       (toot["id"], toot["url"], toot["content"], json.dumps(toot, cls=DateTimeEncoder, default=str, sort_keys=True)))
        connection.commit()
    # TODO option to remove it otherwise (always?)
    connection.close()


def prep_data(connection, cursor):

    connection = sqlite3.connect(options.dbfile)
    cursor = connection.cursor()

    print('retrieving toots from database, please wait ...')
    cursor.execute("SELECT * FROM toots ORDER BY id DESC")
    contents = cursor.fetchall()
    print("retrieved %d toots" % len(contents))

    mydata = []

    for i in range(len(contents)):
        try:
            soup = BeautifulSoup(contents[i][2], 'html.parser')
            # cleaned = clean_html(html.fromstring(contents[i][2])).text_content()
            cleaned = soup.get_text(" ")
            allinfo = json.loads(contents[i][3])


            if cleaned == "":
                continue

            mydata += [[contents[i][0], contents[i][1], cleaned, allinfo]]
        except lxml.etree.ParserError as e:
            print("parse error %s" % e)

    connection.close()

    return mydata



if comm[0][0] == 'download':
    print('going to download entire toot history')
    download_command()

elif comm[0][0] == 'serve':

    print('going to serve')

    app = Flask(__name__)

    @app.route('/fetch')
    def fetch_toots():
        download_command()
        return flask.redirect("/")

    @app.route('/reload/<id>', methods=['POST', 'GET'])
    def reload_toot(id=None):
        reload_command(id)
        return flask.redirect("/#{}".format(id))
        
    @app.route('/')
    def hello_world():

        mydata = prep_data(connection, cursor)

        output = """
        <style>
        table {
          table-layout: fixed;
          border: 1px solid black;
          width: 100%%;
          border-collapse: collapse;
        }
        td.fav {
          background: gray;
          width: 40px;
        }
        td.link {
          width: 40px;
        }
        tr:nth-child(even) {
          background-color: #dacaca;
        }
        </style>
        <a href=\"/fetch\">fetch toots</a>
        %s
        <table>
        %s
        </table>
        """ % \
           (mydata[0][3].keys(),
            "".join(
                [("<tr><td class=\"fav\">" +
                  ("<a id=\"%s\"></a>" % x[3].get("id")) +
                  "<a href=\"%s\">%s ⭐ / %s ♻️</a></td><td class=\"link\"><a href=\"%s\">%s</a></td><td>%s</td></tr>") %
                 ("/reload/{}".format(x[3].get("id")),
                  x[3].get("favourites_count"),
                  x[3].get("reblogs_count"),
                  x[1],
                  "view",
                  x[2]) for x in mydata]))

        return output

    if __name__ == '__main__':
        app.run(host='localhost', port=5000)

    

